/**
 * Copyright (c) Huawei Technologies Co., Ltd. 2019-2021. All rights reserved.
 */

#ifndef PANDA_RUNTIME_ECMASCRIPT_INTERPRETER_INL_H
#define PANDA_RUNTIME_ECMASCRIPT_INTERPRETER_INL_H

#include "plugins/ecmascript/runtime/class_linker/program_object-inl.h"
#include "plugins/ecmascript/runtime/ecma_string.h"
#include "plugins/ecmascript/runtime/ecma_vm.h"
#include "plugins/ecmascript/runtime/global_env.h"
#include "plugins/ecmascript/runtime/interpreter/ecma-interpreter.h"
#include "plugins/ecmascript/runtime/interpreter/interpreter.h"
#include "plugins/ecmascript/runtime/interpreter/js_frame-inl.h"
#include "plugins/ecmascript/runtime/interpreter/slow_runtime_stub.h"
#include "plugins/ecmascript/runtime/js_generator_object.h"
#include "plugins/ecmascript/runtime/js_tagged_value.h"
#include "plugins/ecmascript/runtime/literal_data_extractor.h"
#include "plugins/ecmascript/runtime/template_string.h"
#include "plugins/ecmascript/runtime/handle_storage_check.h"
#include "plugins/ecmascript/compiler/ecmascript_extensions/thread_environment_api.h"
#include "include/method-inl.h"
#include "include/runtime_notification.h"
#include "libpandafile/code_data_accessor.h"
#include "libpandafile/file.h"
#include "libpandafile/method_data_accessor.h"

namespace ark::ecmascript {
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wvoid-ptr-dereference"
#pragma clang diagnostic ignored "-Wgnu-label-as-value"
#elif defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
#endif

class JSInvokeHelper : public InvokeHelperDynamic {
public:
    ALWAYS_INLINE inline static void InterpreterExecute(ManagedThread *thread, const uint8_t *pc, Frame *frame)
    {
        JSThread *jsThread = JSThread::Cast(thread);

        // Get current functional object
        JSTaggedValue func = jsThread->GetFunctionalObject();
        ObjectHeader *lexicalEnv = jsThread->GetInvocationLexicalEnv().GetHeapObject();

        JSFunction *jsFunction = JSFunction::Cast(func.GetHeapObject());
        ObjectHeader *constantPool = jsFunction->GetConstantPool().GetHeapObject();

        // Init EcmascriptEnvironment
        EcmascriptEnvironment *newEnv = JSFrame::GetJSEnv(frame);
        new (newEnv) EcmascriptEnvironment(constantPool, lexicalEnv, func.GetHeapObject());

        InvokeHelperDynamic::InterpreterExecute(thread, pc, frame);
    }

    ALWAYS_INLINE inline static coretypes::TaggedValue CompiledCodeExecute(ManagedThread *thread, Method *method,
                                                                           uint32_t numArgs,
                                                                           coretypes::TaggedValue *args)
    {
        auto ret = InvokeHelperDynamic::CompiledCodeExecute(thread, method, numArgs, args);
        return ret;
    }

    ALWAYS_INLINE static Frame *CreateFrame(ManagedThread *thread, uint32_t nregsSize, Method *method, Frame *prev,
                                            uint32_t nregs, uint32_t numActualArgs)
    {
        return ::ark::CreateFrame<JSEnv>(thread->GetStackFrameAllocator(), nregsSize, method, prev, nregs,
                                         numActualArgs);
    }
};

JSTaggedValue EcmaInterpreter::ExecuteNative(EcmaRuntimeCallInfo *info)
{
    JSThread *thread = info->GetThread();
    uint32_t numArgs = info->GetInternalArgsNum();
    JSTaggedValue fnObject = info->GetFunction().GetTaggedValue();

    ASSERT(!thread->HasPendingException());

    Frame *currentFrame = thread->GetCurrentFrame();
    bool isCompiled = thread->IsCurrentFrameCompiled();

    // Boundary frame expected for c2i call
    ASSERT(!isCompiled || StackWalker::IsBoundaryFrame<FrameKind::INTERPRETER>(currentFrame));

    Method *method = ECMAObject::Cast(fnObject.GetHeapObject())->GetCallTarget();
    ASSERT(method->GetNumVregs() == 0);

    uint32_t numDeclaredArgs = method->GetNumArgs();
    uint32_t nregs = std::max(numDeclaredArgs, numArgs);
    Frame *frame = JSFrame::CreateNativeFrame(thread, method, currentFrame, nregs, numArgs);
    JSFrame::InitNativeFrameArgs(frame, numArgs, info->GetInternalArgs());

    thread->SetCurrentFrameIsCompiled(false);
    thread->SetCurrentFrame(frame);
    JSTaggedValue retValue = JSFrame::ExecuteNativeMethod(thread, frame, method, numArgs);
    ASSERT(thread->GetCurrentFrame() == frame);
    thread->SetCurrentFrameIsCompiled(isCompiled);
    thread->SetCurrentFrame(frame->GetPrevFrame());

    JSFrame::DestroyNativeFrame(thread, frame);

    return retValue;
}

JSTaggedValue EcmaInterpreter::ExecuteInvoke(EcmaRuntimeCallInfo *info, JSTaggedValue fnObject)
{
    JSThread *thread = info->GetThread();

    ASSERT(!thread->HasPendingException());
    ASSERT(fnObject.IsJSFunction());

    JSFunction *jsFunction = JSFunction::Cast(fnObject.GetHeapObject());
    Method *method = jsFunction->GetCallTarget();

    // Set current functional object to thread so we can get it from InvokeHelper
    thread->SetFunctionalObject(fnObject);
    thread->SetInvocationLexicalEnv(jsFunction->GetLexicalEnv());
    TaggedValue retValue =
        method->InvokeDyn<JSInvokeHelper>(thread, info->GetInternalArgsNum(), info->GetInternalArgs());

    return JSTaggedValue(retValue);
}

JSTaggedValue EcmaInterpreter::ExecuteInEnv(EcmaRuntimeCallInfo *info, JSTaggedValue fnObject)
{
    JSThread *thread = info->GetThread();
    [[maybe_unused]] HandleStorageCheck handleStorageCheck(thread);
    if (UNLIKELY(thread->HasPendingException())) {  // NOTE(vpukhov): replace with assert
        return JSTaggedValue::Undefined();
    }

    if (UNLIKELY(!fnObject.IsCallable())) {  // NOTE(vpukhov): replace with assert
        JSHandle<JSObject> error =
            thread->GetEcmaVM()->GetFactory()->GetJSError(ErrorType::TYPE_ERROR, "is not callable");
        thread->SetException(error.GetTaggedValue());
        return JSTaggedValue::Exception();
    }

    Method *method = ECMAObject::Cast(fnObject.GetHeapObject())->GetCallTarget();

    if (method->IsNative()) {
        auto currentFrame = thread->GetCurrentFrame();

        if (thread->IsCurrentFrameCompiled() && !StackWalker::IsBoundaryFrame<FrameKind::INTERPRETER>(currentFrame)) {
            C2IBridge bridge {0, reinterpret_cast<uintptr_t>(currentFrame), COMPILED_CODE_TO_INTERPRETER,
                              thread->GetNativePc()};
            // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
            thread->SetCurrentFrame(reinterpret_cast<Frame *>(&bridge.v[1]));
            JSTaggedValue retVal = ExecuteNative(info);
            // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
            ASSERT(thread->GetCurrentFrame() == reinterpret_cast<Frame *>(&bridge.v[1]));
            thread->SetCurrentFrameIsCompiled(true);
            thread->SetCurrentFrame(currentFrame);
            return retVal;
        }
        return ExecuteNative(info);
    }

    return ExecuteInvoke(info, fnObject);
}

JSTaggedValue EcmaInterpreter::Execute(EcmaRuntimeCallInfo *info)
{
    return ExecuteInEnv(info, info->GetFunction().GetTaggedValue());
}

JSTaggedValue EcmaInterpreter::GeneratorReEnterInterpreter(JSThread *thread, JSHandle<GeneratorContext> context)
{
    if (UNLIKELY(thread->HasPendingException())) {
        return JSTaggedValue::Exception();
    }

    JSHandle<JSFunction> func = JSHandle<JSFunction>::Cast(JSHandle<JSTaggedValue>(thread, context->GetMethod()));
    Method *method = func->GetCallTarget();

    JSTaggedValue pcOffset = context->GetBCOffset();
    auto format = method->GetPandaFile()->GetHeader()->quickenedFlag != 0U ? BytecodeInstruction::Format::V8
                                                                           : BytecodeInstruction::Format::PREF_V8;
    const uint8_t *resumePc =
        // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
        method->GetInstructions() + static_cast<uint32_t>(pcOffset.GetInt()) + BytecodeInstruction::Size(format);

    auto *acc = reinterpret_cast<TaggedValue *>(ToUintPtr(*context) + context->GetAccOffset());
    uint32_t nregs = context->GetNRegs().GetInt();
    TaggedArray *regsArray = TaggedArray::Cast(context->GetRegsArray().GetHeapObject());
    auto *regs = reinterpret_cast<coretypes::TaggedValue *>(regsArray->GetData());
    ASSERT(nregs == regsArray->GetLength());

    // Set current functional object to thread so we can get it from InvokeHelper
    thread->SetFunctionalObject(func.GetTaggedValue());
    thread->SetInvocationLexicalEnv(context->GetLexicalEnv());
    TaggedValue value = method->InvokeContext<JSInvokeHelper>(thread, resumePc, acc, nregs, regs);

    return JSTaggedValue(value);
}

void EcmaInterpreter::ChangeGenContext([[maybe_unused]] JSThread *thread,
                                       [[maybe_unused]] JSHandle<GeneratorContext> context)
{
    LOG(DEBUG, INTERPRETER) << "ChangeGenContext";
}

void EcmaInterpreter::ResumeContext([[maybe_unused]] JSThread *thread)
{
    LOG(DEBUG, INTERPRETER) << "ResumeContext";
}

#if defined(__clang__)
#pragma clang diagnostic pop
#elif defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
}  // namespace ark::ecmascript
#endif  // PANDA_RUNTIME_ECMASCRIPT_INTERPRETER_INL_H
